| Printing Swing Components in Java 1.2 |
|---|
java.awt.print package. This differs from Java 1.1 printing in
that you can obtain high-resolution results (not just blown up screen shots) and
that you have much better control over the print jobs. This section of the Quick Swing
Tutorial for AWT Programmers describes how to use this printing package to
print Swing components. In particular, it illustrates two important concepts not
sufficiently described in the API or in any of the other articles, book
chapters, or on-line tutorials on Java 1.2 printing that I've seen:
printComponent
routine to which you can pass any component you want printed. The component to
be printed need not have a print method, implement any particular
interface, or in fact do anything special at all. In fact, if you don't really
care to understand how printing works behind the scenes, you can skip reading
this tutorial and simply download PrintUtilities.java
and call PrintUtilities.printComponent(componentToBePrinted)
whenever you want to print the contents of a window. PrinterJob object, pass a Printable to
its setPrintable method, call printDialog to pop up
an OS-specific print dialog, and finally, assuming the user hasn't cancelled
the printing from the dialog (you check this by testing the return value of
printDialog), call print on the
PrinterJob.
Note that the role of the Printable is to define a
print method showing how the actual drawing to the printer will
be done. That's described in the next section.
Here's an example:
PrinterJob printJob = PrinterJob.getPrinterJob();
printJob.setPrintable(this);
if (printJob.printDialog())
try {
printJob.print();
} catch(PrinterException pe) {
System.out.println("Error printing: " + pe);
}Here's a representative sample of what you see when you call
printDialog:
Printable
that is passed to setPrintable must have a print
method that describes how to send drawing to the printer.
In general, the first step is to decide what to do for different pages of
your print job, since Java repeatedly calls print with higher and
higher page indexes until print returns
NO_SUCH_PAGE. In the specific case of printing Swing components,
however, you only have a single page. So you return PAGE_EXISTS
for index 0, NO_SUCH_PAGE otherwise.
The second step is to start drawing. In general, you can do any sort of
drawing you want. In the specific case of printing Swing components, however,
your drawing should just be a high-resolution version of what the component
looks like on the screen. So you cast the Graphics object to
Graphics2D, scale the resolution to the printer, and call the
component's paint method with this scaled
Graphics2D. Note that it is this reliance on the
Graphics2D's coordinate transformations that prevents this
capability from being available in the Java 1.1 version of Swing. For more
details on Graphics2D, please see my separate Java2D tutorial.
Here's an example:
public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
if (pageIndex > 0) {
return(NO_SUCH_PAGE);
} else {
Graphics2D g2d = (Graphics2D)g;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// Turn off double buffering
componentToBePrinted.paint(g2d);
// Turn double buffering back on
return(PAGE_EXISTS);
}
}
paintComponent method. However, in the
specific case of printing, it can be a huge problem. First, since printing
components relies on scaling the coordinate system and then simply calling the
component's paint method, if double buffering is enabled printing amounts to
little more than scaling up the buffer (off-screen image). This results in ugly
low-resolution printing like you already had available. Secondly, sending these
huge buffers to the printer results in huge print spooler files which take a
very long time to print.
Consequently, you need to make sure double buffering is turned off before you
print. If you have only a single JPanel or other
JComponent, you can call setDoubleBuffered(false) on
it before calling the paint method, and
setDoubleBuffered(true) afterwards. However, this suffers from the
flaw that if you later nest another container inside, you're right back where
you started from. A much better solution is to globally turn off double
buffering via
RepaintManager currentManager = RepaintManager.currentManager(c); currentManager.setDoubleBufferingEnabled(false);and then to re-enable it after calling paint via the
setDoubleBufferingEnabled(true). (Thanks to Bob Evans for suggesting
RepaintManager instead of the much uglier recursive
descent method I was using to globally disable and re-enable double
buffering.) Although this will completely fix the problem with low-resolution
printouts, if the components have large, complex filled backgrounds you can
still get big spool files and slow printing.
print method is nothing more than to
scale the Graphics, turn off double buffering, and call
paint, there is no particular reason to put that print
method in the component being printed. And requiring printable components to
directly implement the Printable interface and define a
print method makes for cumbersome code and prevents you from
printing components that didn't plan for it. A better approach is to put the
print method in an arbitrary object and tell that object whose
paint method to call back to. This lets you build a generic
printComponent method to which you simply pass the component you
want printed.
import java.awt.*;
import javax.swing.*;
import java.awt.print.*;
public class PrintUtilities implements Printable {
private Component componentToBePrinted;
public static void printComponent(Component c) {
new PrintUtilities(c).print();
}
public PrintUtilities(Component componentToBePrinted) {
this.componentToBePrinted = componentToBePrinted;
}
public void print() {
PrinterJob printJob = PrinterJob.getPrinterJob();
printJob.setPrintable(this);
if (printJob.printDialog())
try {
printJob.print();
} catch(PrinterException pe) {
System.out.println("Error printing: " + pe);
}
}
public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
if (pageIndex > 0) {
return(NO_SUCH_PAGE);
} else {
Graphics2D g2d = (Graphics2D)g;
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
disableDoubleBuffering(componentToBePrinted);
componentToBePrinted.paint(g2d);
enableDoubleBuffering(componentToBePrinted);
return(PAGE_EXISTS);
}
}
public static void disableDoubleBuffering(Component c) {
RepaintManager currentManager = RepaintManager.currentManager(c);
currentManager.setDoubleBufferingEnabled(false);
}
public static void enableDoubleBuffering(Component c) {
RepaintManager currentManager = RepaintManager.currentManager(c);
currentManager.setDoubleBufferingEnabled(true);
}
}
JPanels, one of which has a custom paintComponent method, and
another of which contains a JButton. A simple call to
PrintUtilities.printComponent prints it.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.print.*;
public class PrintExample extends JFrame
implements ActionListener {
public static void main(String[] args) {
new PrintExample();
}
public PrintExample() {
super("Printing Swing Components");
WindowUtilities.setNativeLookAndFeel();
addWindowListener(new ExitListener());
Container content = getContentPane();
JButton printButton = new JButton("Print");
printButton.addActionListener(this);
JPanel buttonPanel = new JPanel();
buttonPanel.setBackground(Color.white);
buttonPanel.add(printButton);
content.add(buttonPanel, BorderLayout.SOUTH);
DrawingPanel drawingPanel = new DrawingPanel();
content.add(drawingPanel, BorderLayout.CENTER);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent event) {
PrintUtilities.printComponent(this);
}
}
Note: also requires WindowUtilities.java
and ExitListener.java,
shown earlier, plus DrawingPanel.java, shown below.
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
public class DrawingPanel extends JPanel {
private int fontSize = 90;
private String message = "Java 2D";
private int messageWidth;
public DrawingPanel() {
setBackground(Color.white);
Font font = new Font("Serif", Font.PLAIN, fontSize);
setFont(font);
FontMetrics metrics = getFontMetrics(font);
messageWidth = metrics.stringWidth(message);
int width = messageWidth*5/3;
int height = fontSize*3;
setPreferredSize(new Dimension(width, height));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
int x = messageWidth/10;
int y = fontSize*5/2;
g2d.translate(x, y);
g2d.setPaint(Color.lightGray);
AffineTransform origTransform = g2d.getTransform();
g2d.shear(-0.95, 0);
g2d.scale(1, 3);
g2d.drawString(message, 0, 0);
g2d.setTransform(origTransform);
g2d.setPaint(Color.black);
g2d.drawString(message, 0, 0);
}
}